/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/interpret/builtins/hosts/fuze/FuzeInterpret_abstract.h"
#include "simodo/interpret/builtins/hosts/fuze/ParsingTableBuilder_LR1.h"
#include "simodo/interpret/builtins/hosts/fuze/ParsingTableBuilder_SLR.h"

#include "simodo/interpret/AnalyzeException.h"
#include "simodo/parser/fuze/fuze_keywords.h"
#include "simodo/ast/NodeFriend.h"

#include <cassert>

namespace simodo::interpret::builtins
{

FuzeInterpret_abstract::FuzeInterpret_abstract(Interpret_interface * inter, 
                                               std::string grammar_name,
                                               parser::Grammar & grammar,
                                               parser::TableBuildMethod method,
                                               bool need_strict_rule_consistency)
    : _interpret(inter)
    , _grammar_name(grammar_name)
    , _grammar(grammar)
    , _method(method)
    , _need_strict_rule_consistency(need_strict_rule_consistency)
    , _main_rule(inout::null_token)
{
    assert(inter);
}

void FuzeInterpret_abstract::reset()
{
}

bool FuzeInterpret_abstract::isOperationExists(ast::OperationCode inner_operation_code) const
{
    parser::FuzeOperationCode operation_code = static_cast<parser::FuzeOperationCode>(inner_operation_code);

    switch(operation_code)
    {
    case parser::FuzeOperationCode::None:
    case parser::FuzeOperationCode::Main:
    case parser::FuzeOperationCode::GlobalScript:
    case parser::FuzeOperationCode::Production:
    case parser::FuzeOperationCode::RemoveProduction:
    case parser::FuzeOperationCode::Reference:
        return true;
    default:
        break;
    }

    return false;
}

InterpretState FuzeInterpret_abstract::performOperation(const ast::Node & op) 
{
    parser::FuzeOperationCode operation_code = static_cast<parser::FuzeOperationCode>(op.operation());

    switch(operation_code)
    {
    case parser::FuzeOperationCode::None:
        return InterpretState::Flow;

    case parser::FuzeOperationCode::Main:
        return parseMain(op);

    case parser::FuzeOperationCode::GlobalScript:
        return parseGlobalScript(op);

    case parser::FuzeOperationCode::Production:
        return parseProduction(op);

    case parser::FuzeOperationCode::RemoveProduction:
        return parseProduction(op, ProductionForWhat::Removing);

    case parser::FuzeOperationCode::Reference:
        return parseRef(op);

    default:
        break;
    }

    throw bormental::DrBormental("FuzeInterpret_abstract::performOperation", 
                "Invalid operation index " + std::to_string(static_cast<size_t>(op.operation())));
}

InterpretState FuzeInterpret_abstract::before_finish(InterpretState state)
{
    return executeFinal(state);
}

InterpretState FuzeInterpret_abstract::parseMain(const ast::Node & op)
{
    return executeMain(op.token());
}

InterpretState FuzeInterpret_abstract::parseGlobalScript(const ast::Node & op)
{
    if (op.branches().empty())
        throw bormental::DrBormental("FuzeInterpret_abstract::parseGlobalScript", "Wrong operation structure");

    return executeGlobalScript(op.token(), op.branches().front());
}

InterpretState FuzeInterpret_abstract::parseProduction(const ast::Node & op, ProductionForWhat what)
{
    std::vector<inout::Token>   pattern;
    inout::Token                direction_token = inout::null_token;
    ast::Node                   action;

    for(const ast::Node & production_node : op.branches()) {
        parser::FuzeOperationCode operation_code = static_cast<parser::FuzeOperationCode>(production_node.operation());

        switch(operation_code) {
        case parser::FuzeOperationCode::Production_Pattern:
            for(const ast::Node & pattern_node : production_node.branches()) {
                // pattern.push_back(pattern_node.operation_symbol());

                inout::LexemeType      type    = inout::LexemeType::Empty;
                const inout::Token &   token = pattern_node.token();

                if (token.type() == inout::LexemeType::Annotation)
                    type = inout::LexemeType::Punctuation;
                else if (token.type() == inout::LexemeType::Id)
                    type = inout::LexemeType::Compound;
                else if (token.type() == inout::LexemeType::Punctuation) {
                    if (token.qualification() == inout::TokenQualification::Keyword) {
                        if (token.lexeme() == parser::ANNOTATION_STRING)
                            type = inout::LexemeType::Annotation;
                        else if (token.lexeme() == parser::NUMBER_STRING)
                            type = inout::LexemeType::Number;
                        else if (token.lexeme() == parser::ID_STRING)
                            type = inout::LexemeType::Id;
                        else
                            throw bormental::DrBormental("FuzeInterpret_abstract::parseProduction", "Wrong operation structure");
                    }
                    else
                        throw bormental::DrBormental("FuzeInterpret_abstract::parseProduction", "Wrong operation structure");
                }
                else
                    throw bormental::DrBormental("FuzeInterpret_abstract::parseProduction", "Wrong operation structure");

                pattern.push_back(inout::Token { {token.lexeme(), type}, token.token(), token.location() });
            }
            break;

        case parser::FuzeOperationCode::Production_Direction: 
            direction_token = production_node.token();
            break;

        case parser::FuzeOperationCode::Production_Script:
            if (!action.branches().empty() || production_node.branches().empty())
                throw bormental::DrBormental("FuzeInterpret_abstract::parseProduction", "Wrong operation structure");
            action = production_node.branches().front();
            break;

        default:
            throw bormental::DrBormental("FuzeInterpret_abstract::parseProduction", "Wrong operation code");
        }
    }

    if (what == ProductionForWhat::Adding)
        return executeProduction(op.token(), pattern, direction_token, action);

    return executeRemovingProduction(op.token(), pattern, action);
}

InterpretState FuzeInterpret_abstract::parseRef(const ast::Node & op)
{
    if (op.branches().empty())
        throw bormental::DrBormental("FuzeInterpret_abstract::parseRef", "Wrong operation structure");

    return executeRef(op.token(), op.branches().front().token().lexeme());
}

InterpretState FuzeInterpret_abstract::executeMain(const inout::Token & main_production)
{
    if (_main_rule.lexeme().empty())
        _main_rule = main_production;
        
    return InterpretState::Flow;
}

InterpretState FuzeInterpret_abstract::executeGlobalScript(const inout::Token & label, const ast::Node & script)
{
    auto it = _grammar.handlers.find(label.lexeme()); 

    if (it != _grammar.handlers.end())
        ast::NodeFriend::insertBranchBegin( it->second, script.branches() );
    else
        _grammar.handlers.insert({label.lexeme(), script});

    return InterpretState::Flow;
}

InterpretState FuzeInterpret_abstract::executeProduction(const inout::Token & production, 
                                            const std::vector<inout::Token> & pattern, 
                                            const inout::Token & direction_token,
                                            const ast::Node & action)
{
    if (pattern.empty())
        throw AnalyzeException("FuzeInterpret_abstract::executeProduction", 
                                production.makeLocation(_interpret->files()),
                                "The right side of the rule cannot be empty");

    parser::RuleReduceDirection direction = parser::RuleReduceDirection::Undefined;   

    if (direction_token.type() != inout::LexemeType::Empty)
        direction = (direction_token.lexeme() == u"<") 
                    ? parser::RuleReduceDirection::LeftAssociative 
                    : parser::RuleReduceDirection::RightAssociative;

    _rules.push_back({production, pattern, action, direction});

    return InterpretState::Flow;
}

InterpretState FuzeInterpret_abstract::executeRemovingProduction(const inout::Token & production, 
                                            const std::vector<inout::Token> & pattern,
                                            const ast::Node & action)
{
    if (pattern.empty())
        throw AnalyzeException("FuzeInterpret_abstract::executeRemoveProduction", 
                                production.makeLocation(_interpret->files()),
                                "The right side of the rule cannot be empty");

    if (!action.branches().empty())
        throw AnalyzeException("FuzeInterpret_abstract::executeRemoveProduction", 
                                production.makeLocation(_interpret->files()),
                                "When deleting products, you do not need to specify an action");

    size_t rule_index = findRule(_rules, production, pattern);

    if (rule_index < _rules.size())
        _rules.erase(_rules.begin()+rule_index);
    else
        throw AnalyzeException("FuzeInterpret_abstract::executeRemoveProduction", 
                                production.makeLocation(_interpret->files()),
                                "The specified rule was not found among the previously defined rules");

    return InterpretState::Flow;
}

InterpretState FuzeInterpret_abstract::executeRef(const inout::Token & , const std::u16string & )
{
    return InterpretState::Flow;
}

InterpretState FuzeInterpret_abstract::executeFinal(InterpretState state)
{
    if (state != InterpretState::Flow || _rules.empty())
        return state;

    // Строим таблицу

    std::unique_ptr<ParsingTableBuilder_abstract>  builder;

    if (_method == parser::TableBuildMethod::SLR)
        builder = std::make_unique<ParsingTableBuilder_SLR>(
                        _interpret->files(), 
                        _interpret->reporter(), 
                        _grammar_name, 
                        _grammar, 
                        _need_strict_rule_consistency);
    else
        builder = std::make_unique<ParsingTableBuilder_LR1>(
                        _interpret->files(), 
                        _interpret->reporter(), 
                        _grammar_name, 
                        _grammar, 
                        _need_strict_rule_consistency);

    if (!builder->build(_rules, _main_rule)) 
        return InterpretState::Complete;

    return InterpretState::Flow;
}

size_t FuzeInterpret_abstract::findRule(const std::vector<parser::GrammarRuleTokens> & rules, const inout::Token &production, const std::vector<inout::Token> &pattern)
{
    size_t i = 0;
    for(; i < rules.size(); ++i) {
        const parser::GrammarRuleTokens & r = rules[i];

        if (r.production.lexeme() != production.lexeme() || r.pattern.size() != pattern.size()) {
            continue;
        }

        size_t j = 0;
        for(; j < r.pattern.size(); ++j)
            if (r.pattern[j].token() != pattern[j].token()) {
                break;
            }

        if (j == r.pattern.size()) {
            break;
        }
    }

    return i;
}
}